package com.liruo.treasureattic.service.user.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liruo.treasureattic.common.httpmsg.exception.CommonResultException;
import com.liruo.treasureattic.common.httpmsg.exception.ValidatorException;
import com.liruo.treasureattic.common.httpmsg.result.Errno;
import com.liruo.treasureattic.common.model.constant.OssConstant;
import com.liruo.treasureattic.common.model.constant.RedisConstant;
import com.liruo.treasureattic.common.model.dto.user.UserLoginReq;
import com.liruo.treasureattic.common.model.dto.user.UserRegisterReq;
import com.liruo.treasureattic.common.model.dto.user.UserUpdateReq;
import com.liruo.treasureattic.common.model.entity.User;
import com.liruo.treasureattic.common.model.vo.user.UserVo;
import com.liruo.treasureattic.common.util.ValidatorUtil;
import com.liruo.treasureattic.service.user.mapper.UserMapper;
import com.liruo.treasureattic.service.user.model.UserConstant;
import com.liruo.treasureattic.service.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RScript;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Collections;

/**
 * @author LYM
 * @description 针对表【user】的数据库操作Service实现
 * @createDate 2022-09-20 18:17:55
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {
    @Resource
    private UserMapper userMapper;
//    @Resource
//    private OssClient ossClient;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private RedissonClient redissonClient;


    @Override
    public Long registerByEmail(UserRegisterReq userRegisterReq) {
        ValidatorUtil.anyNull(userRegisterReq);

        String code = userRegisterReq.getCode();
        ValidatorUtil.anyNull(new String[]{code}, "需要验证码");

        String username = userRegisterReq.getUsername();
        String password = userRegisterReq.getPwd();
        ValidatorUtil.anyNull(new String[]{
                username,
                password
        }, "username or password null");
        ValidatorUtil.checkUserName(username, UserConstant.MIN_NAME, UserConstant.MAX_NAME);
        ValidatorUtil.checkPwd(password, UserConstant.MIN_PWD, UserConstant.MAX_PWD);

        //进行验证验证码
        this.checkCode(userRegisterReq.getEmail(), code);

        //进行username检查， username必须不存在=====这只是保险，正常应该先发送检查username的请求
        if (this.existsByUn(username)) {
            throw new CommonResultException(Errno.PARAMS, "username exists");
        }

        //保存avatar
        try {
            String avatarUrl = saveAvatarIfHas(userRegisterReq.getAvatar(),
                    userRegisterReq.getAvatarUrl(), username);
            userRegisterReq.setAvatarUrl(avatarUrl);
        } catch (Exception e) {
            if(log.isDebugEnabled()){
                log.debug("user[{}] save avatar fail",username, e);
            }
        }

        User user = new User();
        user.setUsername(username);
        BeanUtil.copyProperties(userRegisterReq, user, CopyOptions.create().ignoreNullValue());

        String encodePwd = passwordEncoder.encode(password);
        user.setPwd(encodePwd);

        this.save(user);
        return user.getId();
    }

    /**
     * 单纯地检查code,任何前缀是ta:user:code: + identity都可以检查
     *  都有3次机会，值是 value+"=1"开始
     *  配合接口:UserControllerImpl#limit3Code使用
     * @param identity redis的key, 注册登录是email
     * @param code 有三次试错机会
     */
    private void checkCode(String identity, String code) {
        String codeKey = RedisConstant.USER_CODE + identity;
        RBucket<String> bucket = redissonClient.getBucket(codeKey);
        String cacheCode = bucket.get();

        //返回nil  过期
        if(StringUtils.isEmpty(cacheCode)){
            throw new CommonResultException(Errno.TIME_OUT, "验证码过期");
        }

        String[] cacheCodes = cacheCode.split("=");

        cacheCode = cacheCodes[0];
        if(cacheCode.equals(code)){
            bucket.deleteAsync();
            return;
        }
        //验证码错误
        //3次用完还是验证错误
        int tryTimes = cacheCodes[1].charAt(0) - '0';
        if(tryTimes == 3){
            throw new CommonResultException(Errno.PARAMS, "最多验证3次,请等待一会重新发送验证码验证");
        }
        //还有机会验证
        RScript script = redissonClient.getScript();
        String scriptStr = """
                if (redis.call('TTL', KEYS[1]) == -2) then
                    return -2;
                else
                    redis.call('set', KEYS[1], ARGV[1],'KEEPTTL');
                    return 1;
                end
                """;
        String sha = script.scriptLoad(scriptStr);

        Long ok = script.evalSha(RScript.Mode.READ_WRITE, sha, RScript.ReturnType.INTEGER,
                Collections.singletonList(codeKey),
                cacheCodes[0] + "=" + (tryTimes + 1));
        log.debug("ok = {}", ok);
        if(ok == -2){
            //突然过期了
            throw new CommonResultException(Errno.TIME_OUT, "验证码过期");
        }
        throw new CommonResultException(Errno.PARAMS, "验证码错误");
    }

    /**
     *
     *   AvatarUrl为null且avatar图片不为null，就上传到oss，并且给userRegisterReq设置为ossUrl
     *                        或者AvatarUrl为null且avatar图片为null，就设置为默认的
     */
    private String saveAvatarIfHas(MultipartFile avatar, String avatarUrl, String username) {
        if(StringUtils.isNotEmpty(avatarUrl)) {
            return avatarUrl;
        }
        if(avatar == null || StringUtils.isEmpty(username)){
            return UserConstant.DEFAULT_AVATAR;
        }
        byte[] bytes;
        try {
            bytes = avatar.getBytes();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String key = OssConstant.AVATAR_ICON + username.hashCode();
        if(StringUtils.isNotEmpty(avatar.getContentType())){
            key +=  "." + avatar.getContentType().split("/")[1];
        }
        return UserConstant.DEFAULT_AVATAR;
//        ossClient.uploadAsync(bytes, key);
//        return ossClient.getUrl(key);
    }

    public boolean existsByUn(String username) {
        return this.getOne(new QueryWrapper<User>()
                .select("id")
                .eq("username", username)
        ) != null;
    }



    @Override
    public UserVo loginByEmail(UserLoginReq loginReq) {
        ValidatorUtil.anyNull(loginReq);
        User user = null;

        String email = loginReq.getEmail();

        if(StringUtils.isNotEmpty(loginReq.getPwd())){
            //pwd登录
            String password = loginReq.getPwd();
            ValidatorUtil.anyNull(new String[]{password, email},"password or email err");
            ValidatorUtil.checkEmail(email);
            ValidatorUtil.checkPwd(password, UserConstant.MIN_PWD, UserConstant.MAX_PWD);

            user = this.getOne(new QueryWrapper<User>()
                    .eq("email", email)
                    .eq("pwd", passwordEncoder.encode(password))
            );
        }else{
            //验证码登录
            String code = loginReq.getCode();
            ValidatorUtil.anyNull(new String[]{code, email},"password or email err");
            ValidatorUtil.checkEmail(email);

            checkCode(email, code);
            user = this.getOne(new QueryWrapper<User>()
                    .eq("email", email));
        }

        if (user == null) {
            throw new CommonResultException(Errno.PARAMS, "can't find the user");
        }
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user, userVo);
        return userVo;
    }


    @Override
    public UserVo getUserVoById(long id) {
        ValidatorUtil.requireGreat0(id);
        User user = this.getById(id);
        if (user == null) {
            throw new ValidatorException(Errno.NOT_EXISTS, "can't find the user");
        }
        UserVo userVo = new UserVo();
        BeanUtil.copyProperties(user, userVo);
        return userVo;
    }

    @Override
    public UserVo updateUserById(UserUpdateReq updating, Long userId) {
        ValidatorUtil.anyNull(updating, userId);

        String username = updating.getUsername();
        String pwd = updating.getPwd();
        String email = updating.getEmail();
        if(StringUtils.isNotEmpty(username)){
            ValidatorUtil.checkUserName(username, UserConstant.MIN_NAME, UserConstant.MAX_NAME);
        }
        if(StringUtils.isNotEmpty(pwd)){
            ValidatorUtil.checkPwd(pwd, UserConstant.MIN_NAME, UserConstant.MAX_NAME);
        }
        if(StringUtils.isNotEmpty(email)){
            ValidatorUtil.checkEmail(email);
        }

        if(updating.getAvatar() != null && StringUtils.isNotEmpty(username)){
            String avatarUrl= saveAvatarIfHas(updating.getAvatar(), updating.getAvatarUrl(), username);
            updating.setAvatarUrl(avatarUrl);
        }

        User user = new User();
        user.setId(userId);
        BeanUtil.copyProperties(updating, user);
        this.updateById(user);

        User byId = this.getById(user.getId());
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(byId, userVo);
        return userVo;
    }

    @Override
    public boolean existsById(Long id) {
        return this.getOne(new QueryWrapper<User>()
                        .select("id")
                .eq("id", id)) != null;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void follow(Long followerId, Long followId, Boolean doFollow) {
        ValidatorUtil.requireGreat0(followerId, followId);
        ValidatorUtil.anyNull(doFollow);
        String opt = doFollow ? "+" : "-";
        if(!this.update(new UpdateWrapper<User>()
                .setSql("follow_cnt = follow_cnt " + opt + " 1")
                .eq("id", followerId))
        || !this.update(new UpdateWrapper<User>()
                .setSql("follower_cnt = follower_cnt " + opt + " 1")
                .eq("id", followId))){
            throw new CommonResultException(Errno.SYSTEM, "update follow_cnt and follower_cnt fail");
        }
    }


}




